import { Injectable } from '@nestjs/common';
import { INestApplication } from '@nestjs/common';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { ConfigService } from '@nestjs/config';

export interface SwaggerFilterOptions {
    tags?: string[];           // 包含的标签
    prefix?: string;          // 包含的路径前缀
    excludeTags?: string[];   // 排除的标签
    excludePrefix?: string;   // 排除的路径前缀
}

@Injectable()
export class SwaggerFilterService {
    private app: INestApplication;
    private baseDocument: any;

    constructor(private readonly configService: ConfigService) {}

    setApplication(app: INestApplication) {
        this.app = app;
        this.generateBaseDocument();
    }

    private generateBaseDocument() {
        if (!this.app) return;

        const config = new DocumentBuilder()
            .setTitle(this.configService.get('app.name', 'NestJS API'))
            .setDescription(this.configService.get('app.description', 'NestJS 启动模板 API 文档'))
            .setVersion(this.configService.get('app.version', '1.0.0'))
            .addBearerAuth(
                {
                    type: 'http',
                    scheme: 'bearer',
                    bearerFormat: 'JWT',
                    name: 'JWT',
                    description: 'Enter JWT token',
                    in: 'header',
                },
                'JWT-auth',
            )
            .build();

        this.baseDocument = SwaggerModule.createDocument(this.app, config);
    }

    async getFilteredDocument(options: SwaggerFilterOptions = {}) {
        if (!this.baseDocument) {
            this.generateBaseDocument();
        }

        // 深拷贝原始文档
        const filteredDocument = JSON.parse(JSON.stringify(this.baseDocument));

        // 如果没有过滤条件，返回完整文档
        if (!options.tags && !options.prefix && !options.excludeTags && !options.excludePrefix) {
            return filteredDocument;
        }

        const filteredPaths = {};
        const usedTags = new Set<string>();
        const usedSchemas = new Set<string>(); // 收集使用的 schema

        // 遍历所有路径
        for (const [path, pathItem] of Object.entries(this.baseDocument.paths)) {
            let shouldIncludePath = true;

            // 检查路径前缀过滤
            if (options.prefix && !path.startsWith(options.prefix)) {
                shouldIncludePath = false;
            }

            // 检查排除路径前缀
            if (options.excludePrefix && path.startsWith(options.excludePrefix)) {
                shouldIncludePath = false;
            }

            if (!shouldIncludePath) continue;

            const filteredPathItem = {};

            // 遍历每个HTTP方法
            for (const [method, operation] of Object.entries(pathItem as any)) {
                if (typeof operation !== 'object' || !operation || !(operation as any).tags) continue;

                let shouldIncludeOperation = true;
                const operationWithTags = operation as any;

                // 检查标签过滤
                if (options.tags && options.tags.length > 0) {
                    const hasMatchingTag = operationWithTags.tags.some((tag: string) =>
                        options.tags.includes(tag)
                    );
                    if (!hasMatchingTag) {
                        shouldIncludeOperation = false;
                    }
                }

                // 检查排除标签
                if (options.excludeTags && options.excludeTags.length > 0) {
                    const hasExcludedTag = operationWithTags.tags.some((tag: string) =>
                        options.excludeTags.includes(tag)
                    );
                    if (hasExcludedTag) {
                        shouldIncludeOperation = false;
                    }
                }

                if (shouldIncludeOperation) {
                    filteredPathItem[method] = operation;
                    // 记录使用的标签
                    operationWithTags.tags.forEach((tag: string) => usedTags.add(tag));
                    // 收集操作中使用的 schemas
                    this.collectSchemasFromOperation(operationWithTags, usedSchemas);
                }
            }

            // 如果该路径下有有效的操作，则包含该路径
            if (Object.keys(filteredPathItem).length > 0) {
                filteredPaths[path] = filteredPathItem;
            }
        }

        // 更新文档的路径
        filteredDocument.paths = filteredPaths;

        // 过滤标签定义，只保留实际使用的标签
        if (filteredDocument.tags) {
            filteredDocument.tags = filteredDocument.tags.filter((tag: any) =>
                usedTags.has(tag.name)
            );
        }

        // 过滤 schemas，只保留实际使用的 schemas
        if (filteredDocument.components && filteredDocument.components.schemas) {
            const filteredSchemas = {};
            // 递归收集所有相关的 schemas
            const allUsedSchemas = new Set<string>();
            usedSchemas.forEach(schemaName => {
                this.collectRelatedSchemas(schemaName, filteredDocument.components.schemas, allUsedSchemas);
            });

            // 只保留使用到的 schemas
            allUsedSchemas.forEach(schemaName => {
                if (filteredDocument.components.schemas[schemaName]) {
                    filteredSchemas[schemaName] = filteredDocument.components.schemas[schemaName];
                }
            });

            filteredDocument.components.schemas = filteredSchemas;
        }

        // 更新文档标题以反映过滤条件
        const filterInfo = this.buildFilterInfo(options);
        if (filterInfo) {
            filteredDocument.info.title += ` (${filterInfo})`;
        }

        return filteredDocument;
    }

    private buildFilterInfo(options: SwaggerFilterOptions): string {
        const parts: string[] = [];

        if (options.prefix) {
            parts.push(`路径前缀: ${options.prefix}`);
        }

        if (options.tags && options.tags.length > 0) {
            parts.push(`标签: ${options.tags.join(', ')}`);
        }

        if (options.excludePrefix) {
            parts.push(`排除路径: ${options.excludePrefix}`);
        }

        if (options.excludeTags && options.excludeTags.length > 0) {
            parts.push(`排除标签: ${options.excludeTags.join(', ')}`);
        }

        return parts.join(' | ');
    }

    /**
     * 从操作中收集使用的 schemas
     */
    private collectSchemasFromOperation(operation: any, usedSchemas: Set<string>) {
        // 检查请求体中的 schemas
        if (operation.requestBody?.content) {
            for (const mediaType of Object.values(operation.requestBody.content)) {
                this.extractSchemaRefs(mediaType, usedSchemas);
            }
        }

        // 检查响应中的 schemas
        if (operation.responses) {
            for (const response of Object.values(operation.responses)) {
                if ((response as any).content) {
                    for (const mediaType of Object.values((response as any).content)) {
                        this.extractSchemaRefs(mediaType, usedSchemas);
                    }
                }
            }
        }

        // 检查参数中的 schemas
        if (operation.parameters) {
            for (const param of operation.parameters) {
                this.extractSchemaRefs(param, usedSchemas);
            }
        }
    }

    /**
     * 从对象中提取 schema 引用
     */
    private extractSchemaRefs(obj: any, usedSchemas: Set<string>) {
        if (!obj) return;

        if (typeof obj === 'object') {
            // 检查 $ref
            if (obj.$ref && typeof obj.$ref === 'string') {
                const schemaName = this.extractSchemaNameFromRef(obj.$ref);
                if (schemaName) {
                    usedSchemas.add(schemaName);
                }
            }

            // 递归检查所有属性
            for (const value of Object.values(obj)) {
                this.extractSchemaRefs(value, usedSchemas);
            }
        } else if (Array.isArray(obj)) {
            // 递归检查数组元素
            for (const item of obj) {
                this.extractSchemaRefs(item, usedSchemas);
            }
        }
    }

    /**
     * 从 $ref 中提取 schema 名称
     */
    private extractSchemaNameFromRef(ref: string): string | null {
        const match = ref.match(/#\/components\/schemas\/(.+)$/);
        return match ? match[1] : null;
    }

    /**
     * 递归收集相关的 schemas（处理 schema 之间的引用关系）
     */
    private collectRelatedSchemas(schemaName: string, allSchemas: any, collectedSchemas: Set<string>) {
        if (collectedSchemas.has(schemaName) || !allSchemas[schemaName]) {
            return;
        }

        collectedSchemas.add(schemaName);
        const schema = allSchemas[schemaName];

        // 从当前 schema 中提取引用的其他 schemas
        const referencedSchemas = new Set<string>();
        this.extractSchemaRefs(schema, referencedSchemas);

        // 递归收集引用的 schemas
        referencedSchemas.forEach(refSchemaName => {
            this.collectRelatedSchemas(refSchemaName, allSchemas, collectedSchemas);
        });
    }
}
